package com.shockweb.common.utils;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Hashtable;
import java.util.Map;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import com.shockweb.common.utils.classmethod.GenericType;
/**
 * class处理类
 * @author: 彭明华
 * History:
 * 2017年5月19日  彭明华创建
 */
public class ClassUtil {
	
    /**
     * 获取方法注解定义的类定义
     * @param method 方法
     * @param clazzDefine 类的定义
     * @param index 当前使用的clazz索引
     * @return Class 返回类定义
     */
    public static Class<?> getClassDefine(Method method,Class<?>[] clazzDefine,int index){
    	if(method!=null){
        	GenericType annotation = null;
        	if(method!=null){
        		annotation = method.getAnnotation(GenericType.class);
        	}
        	if(annotation!=null){
        		if(annotation.type().length>index){
        			return annotation.type()[index];
        		}else{
        			return null;
        		}
        	}else{
        		String name = null;
            	if(method!=null){
            		Type[] types = method.getGenericParameterTypes();
            		if(types!=null && types.length>0){
            			name = types[0].toString();
            			int start = -1;
            			int i=index;
            			while(i>0){
            				int tmp = name.indexOf(',',start+1);
            				start = name.indexOf('<',start+1);
            				if(start<0 || (tmp>=0 && tmp<start)){
            					start = tmp;
            				}
            				i--;
            			}
            			int end = name.indexOf('<',start+1);
            			int tmp = name.indexOf(',',start+1);
            			if(end<0 || (tmp>=0 && tmp<end)){
        					end = tmp;
        				}
            			if(end<0){
            				end = name.indexOf('>');
            			}
            			try{
            				return Thread.currentThread().getContextClassLoader().loadClass(name.substring(start+1,end).trim());
            			}catch(Exception e){
            			}
            			try{
            				return Class.forName(name.substring(start+1,end).trim());
            			}catch(Exception e){
            			}
            		}
            	}
        	}
    	}else if(clazzDefine!=null){
    		if(clazzDefine.length>index){
    			return clazzDefine[index];
    		}else{
    			return null;
    		}
    	}
		return null;
    }

	public static final String NullKey = "null";
	
	private static Map<String,Class<?>> classes = new Hashtable<String,Class<?>>();
	
	/**
	 * 获取clazz的对应的class
	 * @param clazz
	 * @return Class
	 * @throws ClassNotFoundException
	 */
	public static Class<?> getClass(String clazz) throws ClassNotFoundException{
		if(clazz.equals(NullKey)){
			return null;
		}else if(String.class.getName().equals(clazz)){
	        return String.class;
    	}else if(int.class.getName().equals(clazz)){
        	return int.class;
    	}else if(Integer.class.getName().equals(clazz)){
    		return Integer.class;
    	}else if(long.class.getName().equals(clazz)){
    		return long.class;
    	}else if(Long.class.getName().equals(clazz)){
    		return Long.class;
    	}else if(StringBuffer.class.getName().equals(clazz)){
    		return StringBuffer.class;
    	}else if(StringBuilder.class.getName().equals(clazz)){
    		return StringBuilder.class;
    	}else if(BigDecimal.class.getName().equals(clazz)){
    		return BigDecimal.class;
    	}else if(double.class.getName().equals(clazz)){
    		return double.class;
    	}else if(Double.class.getName().equals(clazz)){
    		return Double.class;
    	}else if(float.class.getName().equals(clazz)){
    		return float.class;
    	}else if(Float.class.getName().equals(clazz)){
    		return Float.class;
    	}else if(short.class.getName().equals(clazz)){
    		return short.class;
    	}else if(Short.class.getName().equals(clazz)){
    		return Short.class;
    	}else if(byte.class.getName().equals(clazz)){
    		return byte.class;
    	}else if(Byte.class.getName().equals(clazz)){
    		return Byte.class;
    	}else if(char.class.getName().equals(clazz)){
    		return char.class;
    	}else if(Character.class.getName().equals(clazz)){
    		return Character.class;
    	}else if(Date.class.getName().equals(clazz)){
    		return Date.class;
    	}else if(java.sql.Date.class.getName().equals(clazz)){
    		return java.sql.Date.class;
    	}else if(Time.class.getName().equals(clazz)){
    		return Time.class;
    	}else if(Timestamp.class.getName().equals(clazz)){
    		return Timestamp.class;
    	}else if(boolean.class.getName().equals(clazz)){
    		return boolean.class;
    	}else if(Boolean.class.getName().equals(clazz)){
    		return Boolean.class;
    	}else if(BigInteger.class.getName().equals(clazz)){
    		return BigInteger.class;
    	}else if(Class.class.getName().equals(clazz)){
    		return Class.class;
		}else if(!classes.containsKey(clazz)){
			classes.put(clazz, Class.forName(clazz, true, getDefaultClassLoader()));
		}
		return classes.get(clazz);
	}
	
	

    /** The set of matches being accumulated. */
    private Set<Class<?>> matches = new HashSet<Class<?>>();
	
    /**
     * Provides access to the classes discovered so far. If no calls have been made to
     * any of the {@code find()} methods, this set will be empty.
     *
     * @return the set of classes that have been discovered.
     */
    public Set<Class<?>> getClasses() {
        return matches;
    }

    /**
     * Attempts to discover classes . Accumulated
     * classes can be accessed by calling {@link #getClasses()}.
     *
     * @param packageNames one or more package names to scan (including subpackages) for classes
     * @throws ClassNotFoundException 
     * @throws IOException 
     */
    public void findInPackages(String[] packageNames) throws IOException, ClassNotFoundException{
    	matches.clear();
    	for(int i=0;i<packageNames.length;i++){
    		findInPackage(packageNames[i]);
    	}
    }
    
    /**
     * Scans for classes starting at the package provided and descending into subpackages.
     * Each class is offered up to the Test as it is discovered, and if the Test returns
     * true the class is retained.  Accumulated classes can be fetched by calling
     * {@link #getClasses()}.
     *
     * @param packageName the name of the package from which to start scanning for
     *        classes, e.g. {@code net.sourceforge.stripes}
     * @throws ClassNotFoundException 
     */
	private void findInPackage(String packageName) throws IOException, ClassNotFoundException{
		packageName = packageName.replace('.', '/');
		java.lang.ClassLoader loader = getDefaultClassLoader();
		Enumeration<URL> urls;
	
		try {
			urls = loader.getResources(packageName);
	    }
	    catch (IOException e) {
	    	throw new IOException("Could not read package: " + packageName + "\n" + e.getMessage());
	    }
	    while (urls.hasMoreElements()) {
	        String urlPath = ((URL)urls.nextElement()).getFile();

			// If it's a file in a directory, trim the stupid file: spec
			if ( urlPath.startsWith("file:") ) {
			    urlPath = urlPath.substring(5);
			}
			// Else it's in a JAR, grab the path to the jar
			if (urlPath.indexOf('!') > 0) {
			    urlPath = urlPath.substring(0, urlPath.indexOf('!'));
			}
			File file = new File(urlPath);
			if ( file.isDirectory() ) {
			    loadImplementationsInDirectory(packageName, file);
			}
			else {
			    loadImplementationsInJar(packageName, file);
			}
	    }
	}
	
	/**
	 * 获取java.lang.ClassLoader
	 * @return java.lang.ClassLoader
	 */
	public static java.lang.ClassLoader getDefaultClassLoader(){
			java.lang.ClassLoader cl = null;
			try {
				cl = Thread.currentThread().getContextClassLoader();
			}
			catch (Throwable ex) {
				// Cannot access thread context ClassLoader - falling back to system class loader...
			}
			if (cl == null) {
				// No thread context class loader -> use class loader of this class.
				cl = ClassUtil.class.getClassLoader();
			}
			return cl;
	}
	
    /**
     * Finds matches in a physical directory on a filesystem.  Examines all
     * files within a directory - if the File object is not a directory, and ends with <i>.class</i>
     * the file is loaded and tested to see if it is acceptable according to the Test.  Operates
     * recursively to find classes within a folder structure matching the package structure.
     *
     * @param parent the package name up to this directory in the package hierarchy.  E.g. if
     *        /classes is in the classpath and we wish to examine files in /classes/org/apache then
     *        the values of <i>parent</i> would be <i>org/apache</i>
     * @param location a File object representing a directory
     * @throws ClassNotFoundException 
     */
    private void loadImplementationsInDirectory(String parent, File location) throws ClassNotFoundException {
        File[] files = location.listFiles();
        StringBuffer builder = null;
        for (int i=0;i<files.length;i++) {
        	File file = files[i];
            builder = new StringBuffer(100);
            builder.append(parent).append("/").append(file.getName());
            String packageOrClass = ( parent == null ? file.getName() : builder.toString() );
            if (file.isDirectory()) {
                loadImplementationsInDirectory(packageOrClass, file);
            }
            else if (file.getName().endsWith(".class")) {
                addIfMatching(packageOrClass);
            }
        }
    }
    

    
    /**
     * Add the class designated by the fully qualified class name provided to the set of
     * resolved classes if and only if it is approved by the Test supplied.
     *
     * @param fqn the fully qualified name of a class
     * @throws ClassNotFoundException 
     */
    private void addIfMatching(String fqn) throws ClassNotFoundException{
        try {
            String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
            java.lang.ClassLoader loader = getDefaultClassLoader();
            Class<?> type = loader.loadClass(externalName);
            matches.add(type);
        }
        catch (ClassNotFoundException e) {
        	throw new ClassNotFoundException("Could not examine class '" + fqn + "' due to a " +
                    e.getClass().getName(),e);
        }
        
    }
    
    /**
     * Finds matching classes within a jar files that contains a folder structure
     * matching the package structure.  If the File is not a JarFile or does not exist a warning
     * will be logged, but no error will be raised.
     *
     * @param parent the parent package under which classes must be in order to be considered
     * @param jarfile the jar file to be examined for classes
     * @throws IOException 
     * @throws ClassNotFoundException 
     */
    private void loadImplementationsInJar(String parent, File jarfile) throws IOException, ClassNotFoundException{
    	JarInputStream jarStream = null;
        try {
            JarEntry entry;
            jarStream = new JarInputStream(new FileInputStream(jarfile));

            while ( (entry = jarStream.getNextJarEntry() ) != null) {
                String name = entry.getName();
                if (!entry.isDirectory() && name.startsWith(parent) && name.endsWith(".class")) {
                    addIfMatching(name);
                }
            }
        } catch (IOException ioe) {
        	throw new IOException("Could not search jar file '" + jarfile + "' for classes matching criteria: due to an IOException" + "\n" +  ioe.getMessage());
        }
        catch (ClassNotFoundException ioe) {
        	throw new ClassNotFoundException("Could not search jar file '" + jarfile + "' for classes matching criteria: due to an IOException" ,  ioe);
        }finally{
        	if(jarStream!=null){
            	try{
            		jarStream.close();
            	}catch(Exception e){
            	}
        	}
        }
    }
    
    

}
